/**
 * dled_ctl.c - 动态数码管控制库 (3461AS-1)
 * by Nixsawe <ziming_cool@126.com>
 * 2021-11-12 ~ 2021-12-17
 * 
 * 为了更方便地使用 3461AS-1 数码管，特封装此库
 * 包含如下函数：
 * void dledShowDP(DLED_T byte, DLED_T dp, DLED_T pos);
 * void dledShow(DLED_T byte, DLED_T pos);
 * void dledShowBytes(DLED_T * a, int start_pos, int length);
 * void dledShowDigits(DLED_T * a, int start_pos, int length);
 * void dledFormat(long number, DLED_T * a, int length);
 * void dledShowNumber(long number, int show_prefix);
 * void dledMakeBuffer(long number, int show_prefix);
 * void dledShowBuffer();
 * void dledSetAll();
 * void dledClear();
 * 
 * 注意，本库中所使用的位置索引，与数码管实际的位置索引是相反的。
 * 数码管实际索引顺序为从右到左，本库中索引顺序为从左到右。
 * 故在本库中 pos=0 代表左数第一组数码管，这样更符合我们的思考方式。
 * 
 * 在使用时，将 dled_ctl.c 添加到工程中，
 * 并将文件所在目录添加到工程的包含目录列表，
 * 然后在要使用本功能的源文件中引用头文件 dled_ctl.h
 */

#include "dled_ctl.h"

#ifdef DLED_PCTL_USE_BYTE_REGISTER
sfr DLED_PCTL = DLED_PCTL_ADDR;
#endif

// 定义数组以存放数字的字节码
DLED_T dled_digit[10] = {
	DLED_DIGIT_0,
	DLED_DIGIT_1,
	DLED_DIGIT_2,
	DLED_DIGIT_3,
	DLED_DIGIT_4,
	DLED_DIGIT_5,
	DLED_DIGIT_6,
	DLED_DIGIT_7,
	DLED_DIGIT_8,
	DLED_DIGIT_9
};

// 缓冲区
DLED_T dled_buffer[8];

/**
 * 点亮一组数码管，小数点单独指定
 */
void dledShowDP(DLED_T byte, DLED_T dp, DLED_T pos)
{
	dledShow(DLED_BYTE_DP(byte, dp), pos);
}

/**
 * 点亮一组数码管
 * pos 为对应的位置，pos 0-7 对应左到右的 8 组数码管
 */
void dledShow(DLED_T byte, DLED_T pos)
{
	// 清除所有位 (这是为了在移动偏移位置时不会导致“残像”)
	DLED_ADDR = 0;

	// 设置偏移位置
# ifdef DLED_PCTL_USE_BYTE_REGISTER
	DLED_PCTL = pos;
# else
	DLED_PCTL_A = !(pos & 1);
	DLED_PCTL_B = !(pos & 2);
	DLED_PCTL_C = !(pos & 4);
# endif

	// 点亮对应的位
	DLED_ADDR = byte;
}

/**
 * 点亮所有数码管
 * 第一个参数，接受一个有 8 个元素的数组，第二个参数为开始位置，第三个参数为数组长度
 * 本函数接受的数组，其元素为转换好的数码管字节码
 */
void dledShowBytes(DLED_T * a, int start_pos, int length)
{
	int i, right = DLED_MIN(length, 8 - start_pos);
	for(i = right - 1; i >= 0; i --)
	{
		dledShow(a[i], start_pos + i);
	}
}

/**
 * 点亮所有数码管
 * 第一个参数，接受一个有 8 个数字的数组，第二个参数为开始位置，第三个参数为数组长度
 * 本函数接受的数组，其元素为数字，本函数会将其自动转换成数码管字节码
 */
void dledShowDigits(DLED_T * a, int start_pos, int length)
{
	int i, right = DLED_MIN(length, 8 - start_pos);
	for(i = right - 1; i >= 0; i --)
	{
		dledShow(dled_digit[a[i]], start_pos + i);
	}
}

/**
 * 格式化字节码
 * 第一个参数，接受一个数字（不超过 8 位 10 进制数），第二个参数为目标数组，第三个参数为数组长度
 * 本函数会将其自动转换成数码管字节码，置于 a 的最后几个字节中
 * 如果 a 长度不足以放下数字或数字超过可表示范围，则 a 被清零，最后一个字节置一个负号的字节码
 */
void dledFormat(long num, DLED_T * a, int length)
{
	int len = 0, i, neg = 0;
	do
	{
		if(num > 99999999 || num < -9999999)
			break;

		if(num < 0)
		{
			num = -num;
			neg = 1;
		}
		for(i = 0; i < 8; i ++)
		{
			if(++ len > length)
				break;
			a[length - i - 1] = dled_digit[num % 10];
			num /= 10;
			if(!num) break;
		}
		if(num)
			break;
		if(neg)
		{
			if(len >= length)
				break;

			a[length - i - 1] = DLED_DIGIT_MINUS;
			len ++;
		}
		if(len < length)
		{
			memset(a, 0, sizeof(DLED_T) * (length - len));
		}

		return;
	} while(0);

	if(length > 0)
	{
		memset(a, 0, sizeof(DLED_T) * length);
		a[length - 1] = DLED_DIGIT_MINUS;
	}
}

/**
 * 点亮所有数码管
 * 第一个参数，接受一个八位数（数显管最多显示 8 位，十进制），第二个参数为 1 代表显示前缀 0，为 0 则不显示
 * 会变缓冲区内容
 */
void dledShowNumber(long number, int show_prefix)
{
	// 根据参数准备缓冲区内容
	dledMakeBuffer(number, show_prefix);
	// 将缓冲区内容打印出来
	dledShowBuffer();
}

/**
 * 预点亮所有数码管
 * 这个函数并不会实际点亮数码管，它仅仅构造好对应的缓冲区，可用 dledShowBuffer 直接进行点亮
 * 直接根据数字计算数码管字节码的速度很慢（单片机主频比较低），用这种方法可以进行优化
 * 会变缓冲区内容
 */
void dledMakeBuffer(long number, int show_prefix)
{
	int i, neg = 0;
	if(number > 99999999 || number < -9999999)
	{
		// 当前精度不足以显示所有位数
		memset(dled_buffer, DLED_DIGIT_MINUS, sizeof(dled_buffer));
		return;
	}

	if(number < 0)
	{
		number = -number;
		neg = 1;
	}
	for(i = 7; i >= 0; i --)
	{
		dled_buffer[i] = dled_digit[number % 10];
		number /= 10;
		if(!number && !show_prefix)
		{
			i --;
			break;
		}
	}
	if(neg)
	{
		dled_buffer[i >= 0 ? i : 0] = DLED_DIGIT_MINUS;
		i --;
	}

	if(i >= 0) memset(dled_buffer, 0, sizeof(DLED_T) * (i + 1));
}

/**
 * 使用缓冲区的数据点亮所有数码管
 */
void dledShowBuffer()
{
	dledShowBytes(dled_buffer, 0, 8);
}

/**
 * 点亮所有数码管的所有位
 * 会变缓冲区内容
 */
void dledSetAll()
{
	unsigned char byte = -1u;
	memset(dled_buffer, byte, sizeof(dled_buffer));
	dledShowBuffer();
}

/**
 * 清空所有数码管的所有位
 * 会变缓冲区内容
 */
void dledClear()
{
	memset(dled_buffer, 0, sizeof(dled_buffer));
	dledShow(0, 0);
}
